/*******************************************************************************
 * Copyright (c) 2011 Olexandr V. Shaposhnikov.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Olexandr V. Shaposhnikov - initial API and implementation and/or initial documentation
 *******************************************************************************/
package com.funkyroach.use.core.mixin;

import java.util.Stack;

import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.ASTVisitor;
import org.eclipse.dltk.ast.declarations.MethodDeclaration;
import org.eclipse.dltk.ast.declarations.ModuleDeclaration;
import org.eclipse.dltk.ast.declarations.TypeDeclaration;
import org.eclipse.dltk.ast.expressions.Expression;
import org.eclipse.dltk.ast.statements.Statement;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.core.mixin.IMixinRequestor;
import org.eclipse.dltk.core.mixin.IMixinRequestor.ElementInfo;

import com.funkyroach.use.core.ast.USClassDeclaration;
import com.funkyroach.use.core.ast.USFuncDeclaration;
import com.funkyroach.use.core.ast.USIScope;
import com.funkyroach.use.core.ast.USModuleDeclaration;
import com.funkyroach.use.core.ast.USVariableDeclaration;

public class USMixinBuildVisitor extends ASTVisitor {

	static class Scope {

		public Scope(String key) {
			this.key = key;
		}

		String key;
	}

	private Stack<Scope> scopeStack = new Stack<Scope>();
	private Scope classScope = null;
	private final ModuleDeclaration moduleDeclaration;
	private final ISourceModule sourceModule;
	private final IMixinRequestor requestor;

	public USMixinBuildVisitor(ModuleDeclaration moduleDeclaration,
			ISourceModule sourceModule, 
			IMixinRequestor requestor) {
		this.moduleDeclaration = moduleDeclaration;
		this.sourceModule = sourceModule;
		this.requestor = requestor;
	}

	public boolean visit(ASTNode astNode) throws Exception {
		if (astNode instanceof USVariableDeclaration) {
			USVariableDeclaration astVar = (USVariableDeclaration) astNode;
			IModelElement varElement = findModelElementFor(astVar);
			String varKey = scopeStack.peek().key + '{'
					+ USMixinModel.MIXIN_VARIABLE_PREFIX + astVar.getName();
			report(varKey, varElement);
		}
		return true;
	}

	public boolean visit(Statement statement) throws Exception {
		//TODO
		return true;
	}

	public boolean visit(Expression s) throws Exception {
		//TODO
		return true;
	}

	public boolean visit(TypeDeclaration typeDecl) throws Exception {
		if (typeDecl instanceof USClassDeclaration) {
			USClassDeclaration astClass = (USClassDeclaration) typeDecl;
			IModelElement classElement = findModelElementFor(astClass);
			String classKey = scopeStack.peek().key + '{'
					+ USMixinModel.MIXIN_CLASS_PREFIX + astClass.getName();

			report(classKey, classElement);
			classScope = new Scope(classKey);
			scopeStack.push(classScope);
		}
		return true;
	}

	public boolean visit(MethodDeclaration statement) throws Exception {
		if (statement instanceof USFuncDeclaration) {
			USFuncDeclaration astMethod = (USFuncDeclaration) statement;
			IModelElement classElement = findModelElementFor(statement);
			String funcKey = scopeStack.peek().key + '{'
					+ USMixinModel.MIXIN_METHOD_PREFIX + astMethod.getName();
			report(funcKey, classElement);
			scopeStack.push(new Scope(funcKey));
		}
		return true;
	}

	public boolean visit(ModuleDeclaration muduleDecl) throws Exception {
		if (muduleDecl instanceof USModuleDeclaration) {
			USModuleDeclaration astModule = (USModuleDeclaration) muduleDecl;
			IModelElement moduleElement = findModelElementFor(astModule);
			String moduleKey = USMixinModel.MIXIN_MODULE_PREFIX
					+ astModule.getName();
			report(moduleKey, moduleElement);
			scopeStack.push(new Scope(moduleKey));
		}
		return true;
	}

	public void endvisitGeneral(ASTNode node) throws Exception {
		if (node instanceof USIScope) {
			scopeStack.pop();
		}
	}

	private IModelElement findModelElementFor(ASTNode astNode)
			throws ModelException {
		return sourceModule.getElementAt(astNode.sourceStart() + 1);
	}

	private void report(String key, IModelElement object) {
		if (requestor != null) {
			ElementInfo info = new IMixinRequestor.ElementInfo();
			info.key = key;
			info.object = object;
			System.out.println(" MIXIN INDEX REPORTED " + info);
			requestor.reportElement(info);
		}
	}
}
